package org.jeecg.common.util;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.methods.PostMethod;
import org.jeecg.common.exception.JeecgBootException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 获取行政区域工具类
 *
 * @author chechen
 * @date 2021-12-18
 */
@Slf4j
public class AdministrativeRegionsUtil {

    private static final String ADMINISTRATIVE_REGIONS_URL = "https://restapi.amap.com/v3/config/district";
    private static final String GEOCODE_REGEO_URL = "https://restapi.amap.com/v3/geocode/regeo";
    private static final String KEY = "e399a25bb21f97d040765b175c8ea8f1";
    private static final String DISTRICTS = "districts";
    private static final String STREET = "street";
    private static final String CITYCODE = "citycode";
    private static final String ADCODE = "adcode";
    private static final String NAME = "name";
    private static final String LEVEL = "level";
    private static final String CENTER = "center";
    private static final char COMMA = ',';
    private static final List<JSONObject> PROVINCE_LIST = new ArrayList<>(34); // 省
    private static final List<JSONObject> CITY_LIST = new ArrayList<>(370); // 市
    private static final List<JSONObject> DISTRICT_LIST = new ArrayList<>(3000); // 区县
    private static final List<JSONObject> STREET_LIST = new ArrayList<>(42000); // 街道
    private static final Map<String, List<JSONObject>> AREA_LIST_MAP = new HashMap<String, List<JSONObject>>() {{
        put("province", PROVINCE_LIST);
        put("city", CITY_LIST);
        put("district", DISTRICT_LIST);
        put(STREET, STREET_LIST);
    }};
    private static final Map<String, String> PROVINCE_ADCODE_MAP = new HashMap<String, String>() {{
        put("410000", "河南省");
        put("440000", "广东省");
        put("150000", "内蒙古自治区");
        put("230000", "黑龙江省");
        put("650000", "新疆维吾尔自治区");
        put("420000", "湖北省");
        put("210000", "辽宁省");
        put("370000", "山东省");
        put("610000", "陕西省");
        put("310000", "上海市");
        put("520000", "贵州省");
        put("500000", "重庆市");
        put("540000", "西藏自治区");
        put("340000", "安徽省");
        put("350000", "福建省");
        put("430000", "湖南省");
        put("460000", "海南省");
        put("320000", "江苏省");
        put("630000", "青海省");
        put("450000", "广西壮族自治区");
        put("640000", "宁夏回族自治区");
        put("330000", "浙江省");
        put("130000", "河北省");
        put("810000", "香港特别行政区");
        put("710000", "台湾省");
        put("820000", "澳门特别行政区");
        put("620000", "甘肃省");
        put("510000", "四川省");
        put("120000", "天津市");
        put("360000", "江西省");
        put("530000", "云南省");
        put("140000", "山西省");
        put("110000", "北京市");
        put("220000", "吉林省");
    }};
    private static final String DB_URL = "jdbc:mysql://192.168.0.145:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true";
    private static final String TABLE_NAME = "mk_area_test";
    private AdministrativeRegionsUtil() {
    }

    /**
     * 重新获取省市区县街道
     */
    public static void reacquire() {
        System.out.println("\n");
        // 清空表
        if (!emptyTable()) return;

        Map<String, String> params = new HashMap<String, String>() {{
            /*
              subdistrict规则：设置显示下级行政区级数
              0：不返回下级行政区（国家）；
              1：返回下一级行政区（省/直辖市）；
              2：返回下两级行政区（市）；
              3：返回下三级行政区（区/县）；
              4：返回下四级行政区（乡/镇/街道）；
              我们直接用4，返回全部的数据
             */
            put("subdistrict", "4");
            put("key", KEY);
        }};
        for (String key : PROVINCE_ADCODE_MAP.keySet()) {
            params.put("keywords", key);
            PostMethod post = HttpClientUtil.post(ADMINISTRATIVE_REGIONS_URL, params);
            InputStream inputStream;
            StringBuffer stringBuffer = new StringBuffer();
            try {
                inputStream = post.getResponseBodyAsStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                String str;
                while ((str = bufferedReader.readLine()) != null)
                    stringBuffer.append(str);
                log.info("请求 " + PROVINCE_ADCODE_MAP.get(key) + " 成功");
            } catch (IOException e) {
                e.printStackTrace();
            }
            JSONObject administrativeRegionsData = JSONObject.parseObject(stringBuffer.toString());
            if (!"1".equals(administrativeRegionsData.getString("status")))
                throw new JeecgBootException("请求 " + PROVINCE_ADCODE_MAP.get(key) + " 行政区域 API 未成功");

            handleJSONArray(administrativeRegionsData.getJSONArray(DISTRICTS));
            log.info(PROVINCE_ADCODE_MAP.get(key) + "数据处理结果: 省 " + PROVINCE_LIST.size() + "; 市 " + CITY_LIST.size() +
                    "; 县 " + DISTRICT_LIST.size() + "; 街道 " + STREET_LIST.size());

            log.info("开始获取 " + PROVINCE_ADCODE_MAP.get(key) + " 的街道 towncode");
            handleStreetTypeTownCode();

            log.info("开始插入 " + PROVINCE_ADCODE_MAP.get(key));
            batchInsert();

            // 清空
            PROVINCE_LIST.clear();
            CITY_LIST.clear();
            DISTRICT_LIST.clear();
            STREET_LIST.clear();
        }

    }

    /**
     * 根据 districts 判断其中的级别（level），存入相应的集合中
     *
     * @param districts 高德 api 中返回的 districts 数组字段
     */
    private static void handleJSONArray(JSONArray districts) {
        districts.toJavaList(JSONObject.class).forEach(provinceJSONObject -> {
            // 删除没用的字段
            provinceJSONObject.remove(CITYCODE);
            // 获取等级，用来判断应该存到那个集合里
            final String LEVEL_VALUE = provinceJSONObject.getString(LEVEL);
            AREA_LIST_MAP.get(LEVEL_VALUE).add(provinceJSONObject);
            // 判断districts集合里还有没有数据，有就继续
            JSONArray nextLevel = provinceJSONObject.getJSONArray(DISTRICTS);
            if (nextLevel.size() > 0) handleJSONArray(nextLevel);
        });
    }

    /**
     * 获取街道的towncode（存到adcode中）
     */
    private static void handleStreetTypeTownCode() {

        final int MAX_NUMBER = 20; // 按每 20 个一组分割
        final int LIMIT = (STREET_LIST.size() + MAX_NUMBER - 1) / MAX_NUMBER; // 计算切分次数
        AtomicInteger index = new AtomicInteger();

        // 开始切割
        List<List<JSONObject>> streetSplitList = Stream.iterate(0, n -> n + 1).limit(LIMIT).parallel().map(a ->
                STREET_LIST.stream().skip(a * MAX_NUMBER).limit(MAX_NUMBER).parallel()
                        .collect(Collectors.toList())).collect(Collectors.toList());

        Map<String, String> params = new HashMap<String, String>() {{
            put("radius", "100");
            put("batch", "true");
            put("extensions", "base");
            put("roadlevel", "1");
            put("key", KEY);
        }};
        ProgressBar progressBar = ProgressBar.build(STREET_LIST.size());
        // 批量查询，每次最多支持查 20 个坐标点
        streetSplitList.forEach(twentyItem -> {
            String centerJoinStr = twentyItem.stream().map(item -> item.getString(CENTER)).collect(Collectors.joining("|"));
            params.put("location", centerJoinStr);
            PostMethod post = HttpClientUtil.post(GEOCODE_REGEO_URL, params);
            InputStream inputStream;
            StringBuffer stringBuffer = new StringBuffer();
            try {
                inputStream = post.getResponseBodyAsStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                String str;
                while ((str = bufferedReader.readLine()) != null)
                    stringBuffer.append(str);
                JSONObject geocodeRegeoData = JSONObject.parseObject(stringBuffer.toString());
                if (!"1".equals(geocodeRegeoData.getString("status")))
                    log.warn("请求此组未成功 > " + twentyItem.stream().map(item -> item.getString(NAME)).collect(Collectors.joining(StringPool.COMMA)));
                List<JSONObject> regeocodes = geocodeRegeoData.getJSONArray("regeocodes").toJavaList(JSONObject.class);
                for (int i = 0; i < regeocodes.size(); i++)
                    twentyItem.get(i).put(ADCODE, regeocodes.get(i).getJSONObject("addressComponent").getString("towncode"));
                index.addAndGet(MAX_NUMBER);
                progressBar.process(twentyItem.size());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

    }

    private static boolean emptyTable() {
        String adcodes = PROVINCE_ADCODE_MAP.keySet()
                .stream().map(code -> StringPool.SINGLE_QUOTE + code + StringPool.SINGLE_QUOTE).collect(Collectors.joining(StringPool.COMMA));
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            try (Connection connection = DriverManager.getConnection(DB_URL, "root", "123456")) {
                if (connection == null) {
                    log.error("获取连接失败");
                    return false;
                } else {
                    log.info("获取连接成功");
                }
                try (PreparedStatement statement = connection.prepareStatement(
                        "delete from " + TABLE_NAME + " where adcode in(" + adcodes + ")")) {
                    statement.executeUpdate();
                    log.info("已清空" + TABLE_NAME);
                }
            }//获取连接
        } catch (Exception e) {
            log.error("删除表数据失败 adcode > " + adcodes);
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private static void batchInsert() {
        // 开始时间
        Long begin = new Date().getTime();
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            try (Connection connection = DriverManager.getConnection(DB_URL, "root", "123456")) {
                if (connection != null) {
                    log.info("获取连接成功");
                } else {
                    log.error("获取连接失败");
                    return;
                }
                //这里必须设置为false，我们手动批量提交
                connection.setAutoCommit(false);
                //这里需要注意，SQL语句的格式必须是预处理的这种，就是values(?,?,...,?)，否则批处理不起作用
                try (PreparedStatement statement = connection.prepareStatement(
                        "insert into " + TABLE_NAME + "(adcode,`name`,`level`,latitude,longitude) values(?,?,?,?,?)")) {
                    // 塞数据
                    List<List<JSONObject>> values = new ArrayList<>(AREA_LIST_MAP.values());
                    JSONObject trash;
                    String latAndLngStr;
                    for (int k = 0; k < 4; k++) {
                        for (int i = 0; i < values.get(k).size(); i++) {
                            trash = values.get(k).get(i);
                            statement.setString(1, trash.getString(ADCODE));
                            statement.setString(2, trash.getString(NAME));
                            statement.setString(3, trash.getString(LEVEL));
                            latAndLngStr = trash.getString(CENTER);
                            for (int j = 0; j < latAndLngStr.length(); j++) {
                                if (latAndLngStr.charAt(j) == COMMA) {
                                    statement.setBigDecimal(4, new BigDecimal(latAndLngStr.substring(0, j)));
                                    statement.setBigDecimal(5, new BigDecimal(latAndLngStr.substring(j + 1)));
                                    statement.addBatch();
                                    break;
                                }
                            }
                        }
                    }
                    // 提交要执行的批处理，防止 JDBC 执行事务处理
                    statement.executeBatch();
                    connection.commit();
                    statement.clearBatch();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 结束时间
        Long end = new Date().getTime();
        // 耗时
        log.info("数据插入花费时间 >>>" + (end - begin) / 1000 + "s");
    }

}
